# 作者：徐宇杰
# -*- coding = utf-8 -*-
# @Time :2024/7/30 下午1:24
# @File:BackTest.py
# @Software:PyCharm
import json
import math
import sys
import warnings
from datetime import date, timedelta

import numpy as np
from tqdm import tqdm
import time
import pandas as pd
import requests
import matplotlib.pyplot as plt

pd.set_option('float_format', lambda x: '%.2f' % x)

warnings.simplefilter(action='ignore', category=FutureWarning)

# 这里用的是ddqh119的账号,账号到期后或者每周刷新后要更换下面的refreshtoken
getAccessTokenUrl = 'https://quantapi.51ifind.com/api/v1/get_access_token'
refreshtoken = (
    'eyJzaWduX3RpbWUiOiIyMDI0LTA4LTA4IDEzOjIyOjE2In0=.eyJ1aWQiOiI3MDQyMzg2NjUiLCJ1c2VyIjp7ImFjY291bnQiOiJkZHFoMTE5IiwiYXV0aFVzZXJJbmZvIjp7ImFwaUZvcm1hbCI6IjEifSwiY29kZUNTSSI6W10sImNvZGVaekF1dGgiOltdLCJoYXNBSVByZWRpY3QiOmZhbHNlLCJoYXNBSVRhbGsiOmZhbHNlLCJoYXNDSUNDIjpmYWxzZSwiaGFzQ1NJIjpmYWxzZSwiaGFzRXZlbnREcml2ZSI6ZmFsc2UsImhhc0ZUU0UiOmZhbHNlLCJoYXNGYXN0IjpmYWxzZSwiaGFzRnVuZFZhbHVhdGlvbiI6ZmFsc2UsImhhc0hLIjp0cnVlLCJoYXNMTUUiOmZhbHNlLCJoYXNMZXZlbDIiOmZhbHNlLCJoYXNSZWFsQ01FIjpmYWxzZSwiaGFzVHJhbnNmZXIiOmZhbHNlLCJoYXNVUyI6ZmFsc2UsImhhc1VTQUluZGV4IjpmYWxzZSwibWFya2V0Q29kZSI6IjE2OzMyOzE0NDsxNzY7MTEyOzg4OzQ4OzEyODsxNjgtMTsxODQ7MjAwOzIxNjsxMDQ7MTIwOzEzNjsyMzI7NTY7OTY7MTYwOzY0OyIsIm1heE9uTGluZSI6MSwibm9EaXNrIjpmYWxzZSwicHJvZHVjdFR5cGUiOiJTVVBFUkNPTU1BTkRQUk9EVUNUIiwicmVmcmVzaFRva2VuRXhwaXJlZFRpbWUiOiIyMDI0LTA5LTA3IDA5OjU5OjI5Iiwic2Vzc3Npb24iOiI4YWE2NDVhNDlkN2I4YjA3YTM1ZjkxZWQzMzM2ZjQ4MCIsInNpZEluZm8iOnt9LCJ0cmFuc0F1dGgiOmZhbHNlLCJ1aWQiOiI3MDQyMzg2NjUiLCJ1c2VyVHlwZSI6Ik9GRklDSUFMIiwid2lmaW5kTGltaXRNYXAiOnt9fX0=.87A2EBFB309F245156D7ED30898C5F75CCBE70D54DDCC244E0B770B7360659DA')
getAccessTokenHeader = {"Content- Type": "application/json", "refresh_token": refreshtoken}
getAccessTokenResponse = requests.post(url=getAccessTokenUrl, headers=getAccessTokenHeader)
accessToken = json.loads(getAccessTokenResponse.content)['data']['access_token']
thsHeaders = {"Content-Type": "application/json", "access_token": accessToken}


# 对传输过来的数据进行转化为dataframe形式
def initialization(thsResponse):
    data0 = json.loads(thsResponse.content)
    data1 = pd.json_normalize(data0['tables'])
    un_nested_lst = []
    for col in data1.columns:
        un_nested_lst.append(data1[col].apply(pd.Series).stack())
    data2 = pd.concat(un_nested_lst, axis=1, keys=data1.columns)
    data2 = data2.reset_index(drop=True)
    return data2


# 对行权价格的计算
def Strike_price_calculation(list_price_change_combination, price1):
    price_threshold = int(list_price_change_combination[-1])
    if price1 > price_threshold:
        price_change = int(list_price_change_combination[2])
        remainder = price1 % price_change
        strike_price1 = price1 - remainder
        if remainder / price_change > 0.5:
            strike_price1 = strike_price1 + price_change
    elif price1 == price_threshold:
        strike_price1 = price_threshold
    else:
        price_threshold = int(list_price_change_combination[-2])
        if price1 > price_threshold:
            price_change = int(list_price_change_combination[1])
            remainder = price1 % price_change
            strike_price1 = price1 - remainder
            if remainder / price_change > 0.5:
                strike_price1 = strike_price1 + price_change
        elif price1 == price_threshold:
            strike_price1 = price_threshold
        else:
            price_change = int(list_price_change_combination[0])
            remainder = price1 % price_change
            strike_price1 = price1 - remainder
            if remainder / price_change > 0.5:
                strike_price1 = strike_price1 + price_change
    return strike_price1


# 对大循环的数据进行导入、形成与更新
def Import_Update_data(df, Futures_code, start_date, end_date, count, price_change_combination, frequency, range_window,
                       i):
    # 导入对应期货的数据
    thsUrl = 'https://quantapi.51ifind.com/api/v1/high_frequency'
    thsPara = {"codes": Futures_code,
               "indicators": "close,open,high,low",
               "starttime": start_date,
               "endtime": end_date,
               "functionpara": {
                   "Interval": str(frequency),
                   "Fill": "Original"
               }
               }
    thsResponse = requests.post(url=thsUrl, json=thsPara, headers=thsHeaders)
    data = json.loads(thsResponse.content)
    df1 = pd.json_normalize(data['tables'])
    try:
        df2 = df1.set_index(['thscode'])
    except Exception as e:
        print(data['errmsg'])
        print(thsPara)
        sys.exit()
    un_nested_list = []
    for col in df2.columns:
        un_nested_list.append(df2[col].apply(pd.Series).stack())
    data = pd.concat(un_nested_list, axis=1, keys=df2.columns)
    # 设置二级索引
    data = data.reset_index()
    data = data.set_index(['thscode', 'time'])
    # 格式化,行转列
    data = data.drop(columns=['level_1'])
    data = data.reset_index()
    # 设置列索引名称
    data.columns = ['thscode', 'time', 'close', 'open', 'high', 'low']
    # 去除NaN值
    data.dropna(inplace=True)
    data = data.reset_index()
    data = data.iloc[:, 1:]
    list_price_change_combination = price_change_combination.split(',')
    for z in range(0, count):
        if z == count - 1:
            data_temp = data.iloc[-range_window:]
        else:
            data_temp = data.iloc[-range_window - count + z + 1:-count + z + 1]
        data_temp = data_temp.reset_index(drop=True)
        upper = data_temp['high'].max()
        lower = data_temp['low'].min()
        UpDown = upper - lower
        otm_range = int(UpDown / 4)
        price1 = round(data_temp.iloc[-1, 2] + otm_range)
        strike_price1 = Strike_price_calculation(list_price_change_combination, price1)
        K_call = int(strike_price1)
        price2 = round(data_temp.iloc[-1, 2] - otm_range)
        strike_price2 = Strike_price_calculation(list_price_change_combination, price2)
        K_put = int(strike_price2)
        df.iloc[i - count + z + 1, 3] = data_temp.iloc[-1, 2]
        df.iloc[i - count + z + 1, 4] = upper
        df.iloc[i - count + z + 1, 5] = lower
        df.iloc[i - count + z + 1, 6] = K_call
        df.iloc[i - count + z + 1, 7] = K_put
        if Futures_code[-3:] == "DCE":
            str_list = list(Futures_code)
            str_list.insert(-4, "-C-{}".format(K_call))
            code_call = ''.join(str_list)
            str_list = list(Futures_code)
            str_list.insert(-4, "-P-{}".format(K_put))
            code_put = ''.join(str_list)
        else:
            str_list = list(Futures_code)
            str_list.insert(-4, "C{}".format(K_call))
            code_call = ''.join(str_list)
            str_list = list(Futures_code)
            str_list.insert(-4, "P{}".format(K_put))
            code_put = ''.join(str_list)
        df.iloc[i - count + z + 1, 0] = code_call
        df.iloc[i - count + z + 1, 1] = code_put
    return df


# 得到保证金的价格
def get_the_margin_price(str_date, code):
    thsUrl = 'https://quantapi.51ifind.com/api/v1/date_sequence'
    thsPara = {"codes": code,
               "startdate": str_date,
               "enddate": str_date,
               "functionpara":
                   {"Fill": "Blank"},
               "indipara":
                   [
                       {
                           "indicator":
                               "ths_trade_deposit_option"
                       }
                   ]
               }
    thsResponse = requests.post(url=thsUrl, json=thsPara, headers=thsHeaders)
    data2 = initialization(thsResponse)
    deposit = data2.iloc[0, 2]
    return deposit


# 得到小循环的数据
def get_the_option_data(code, start_date, close_date, frequency, i, df, date_frame, type0):
    data_flag = 0
    fore_code_length = len(date_frame.iloc[0, 0])
    length = len(df)
    thsUrl = 'https://quantapi.51ifind.com/api/v1/high_frequency'
    thsPara = {"codes": code,
               "indicators": "close",
               "starttime": start_date,
               "endtime": close_date,
               "functionpara": {
                   "Interval": str(frequency),
                   "Fill": "Original"
               }
               }
    thsResponse = requests.post(url=thsUrl, json=thsPara, headers=thsHeaders)
    data2 = initialization(thsResponse)
    if data2.shape[1] == 1:
        data_flag = 1
        return df, data_flag
    data2 = data2.iloc[:, 1:]
    length0 = len(data2)
    min_length = min(length - i, length0)
    day_limit1 = date_frame.loc[date_frame['fore_code'] == code[:fore_code_length], 'due_date']
    day_limit = date.fromisoformat(day_limit1.tolist()[0])
    recent_index = i + 1
    if type0 == 0:
        x = 10
    else:
        x = 13
    df.iloc[recent_index - 1, x] = data2.iloc[0, 1]
    for j in range(0, min_length - 1):
        if date.fromisoformat(data2.iloc[j, 0][:10]) >= day_limit:
            break
        min_allowed_count = length - recent_index
        for m in range(0, min_allowed_count):
            if date.fromisoformat(df.iloc[recent_index + m, 2][:10]) >= day_limit:
                break
            if df.iloc[recent_index + m, 2] != data2.iloc[j + 1, 0]:
                # 没找到对应时间
                df.iloc[recent_index + m, x] = data2.iloc[j, 1]
                # 到最后一个时间点
                if recent_index + m == length - 1:
                    break
            else:
                # 找到对应的时间
                df.iloc[recent_index + m, x] = data2.iloc[j + 1, 1]
                recent_index = recent_index + m + 1
                break
    return df, data_flag


# 对开始时间和截至时间的约束
def Date_determination(date0, number2, key_months, start_or_end):
    list_key_months = key_months.split(',')
    length_key_months = len(list_key_months)
    if date0 == "":
        number3 = int(str(number2)[:2])
        number4 = int(str(number2)[-2:])
        if length_key_months == 3:
            key_month = list_key_months[2]
            # 这里不采用str(int(key_month)+1),是因为期权有开始日期,不能过早
            if number4 < 5:
                month = '01'
                year = "20" + str(number3)
            elif number4 < int(key_month):
                month = '05'
                year = "20" + str(number3)
            else:
                month = str(int(key_month))
                year = "20" + str(number3)
        else:
            if number4 == 1:
                month = "12"
                year = "20" + str(number3 - 1)
            elif 1 < number4 < 11:
                month = "0" + str(number4 - 1)
                year = "20" + str(number3)
            else:
                month = str(number4 - 1)
                year = "20" + str(number3)
        date0 = year + "-" + month + "-01 00:00:00"
    else:
        if len(date0) == 10:
            date0 = date0 + " 00:00:00"
        index1 = date0.find('-')
        year = int(date0[:index1]) - 2000
        index2 = date0.find('-', index1 + 1)
        month = int(date0[index1 + 1:index2])
        if length_key_months == 3:
            key_month = list_key_months[2]
            if month < 5:
                month = '05'
            elif month < int(key_month):
                month = key_month
            else:
                month = '01'
                year = year + 1
        else:
            if month == 12:
                year = year + 1
                month = list_key_months[0]
            else:
                month = list_key_months[month]
        number1 = int(str(year) + month)
        if (start_or_end == 0 and number1 >= number2) or (start_or_end == 1 and number1 <= number2):
            return date0
        else:
            number3 = int(str(number2)[:2])
            number4 = int(str(number2)[-2:])
            if length_key_months == 3:
                key_month = list_key_months[2]
                if number4 < 5:
                    month = '01'
                    year = "20" + str(number3)
                elif number4 < int(key_month):
                    month = '05'
                    year = "20" + str(number3)
                else:
                    month = str(int(key_month))
                    year = "20" + str(number3)
            else:
                if number4 == 1:
                    month = "12"
                    year = "20" + str(number3 - 1)
                elif 1 < number4 < 11:
                    month = "0" + str(number4 - 1)
                    year = "20" + str(number3)
                else:
                    month = str(number4 - 1)
                    year = "20" + str(number3)
            date0 = year + "-" + month + "-01 00:00:00"
    return date0


window1 = 0
window2 = 0
window_change = 0


class Back_Test:
    # 初始化函数
    def __init__(self):
        """
               初始化方法，用于设置对象的属性
               contract_multiplier: 合约乘数,price_change_combination: 行权价价格变化的组合,key_months: 主力合约的月份统计值
               code: 要做回测的品种金融代码,frequency: 行情的频率,start_date: 做回测的开始时间,end_date: 做回测的结束时间
               account: 账户的总数目,risk: 做回测的风险度,profit_stop: 止盈线,loss_stop: 止损线,quant: 分批处理的批数
               range_window: 观测移动窗口的大小,local_data: 本地数据,save_path: 保存运行完数据的地址
               number_of_stop_losses: 止损的次数,number_of_profit_exits: 止盈的次数,result: 结果表,income: 总的收入
               problematic_options: 遇到两种问题的期权表格,date_frame:所有遇到的期权的到期日的统计，final_frame:对多次回测的统计表
        """
        # 如果最新的期权更新的话，需要对'Option end date'进行更新，如果行权价的价格变化出现变化，需要对'Connection'进行更新
        data = {
            'Thscode': ['AGZL.SHF', 'ALZL.SHF', 'AZL.DCE', 'CFZL.CZC', 'CUZL.SHF', 'CZL.DCE', 'EBZL.DCE', 'EGZL.DCE',
                        'IZL.DCE', 'LZL.DCE', 'MZL.DCE', 'PFZL.CZC', 'PGZL.DCE', 'PPZL.DCE', 'PZL.DCE', 'RBZL.SHF',
                        'RMZL.CZC', 'RUZL.SHF', 'TAZL.CZC', 'VZL.DCE', 'YZL.DCE', 'ZNZL.SHF'],
            'Connection': ['20,50,100,2500,5000', '50,100,200,10000,20000', '25,50,100,2500,5000',
                           '100,200,400,10000,20000', '500,1000,2000,40000,80000', '10,20,40,1000,3000',
                           '50,100,200,5000,10000', '25,50,100,2500,5000', '5,10,20,300,1000',
                           '50,100,200,5000,10000', '25,50,100,2000,5000', '100,200,400,5000,10000',
                           '25,50,100,2000,6000', '50,100,200,5000,10000', '50,100,200,5000,10000',
                           '20,50,100,2500,5000', '25,50,100,2500,5000', '100,250,500,10000,25000',
                           '50,100,200,5000,10000', '50,100,200,5000,10000', '50,100,200,5000,10000',
                           '100,200,500,10000,25000'],
            'Option start date': ['2303', '2010', '2211', '1905', '1901', '1901', '2307', '2307', '2002', '2009',
                                  '1809', '2401', '2011', '2009', '2109', '2303', '2005', '1905', '2003', '2009',
                                  '2211', '2010'],
            'Option end date': ['2504', '2501', '2507', '2505', '2504', '2507', '2507', '2507', '2507', '2507',
                                '2507', '2411', '2507', '2507', '2507', '2503', '2505', '2501', '2505', '2507',
                                '2507', '2411']
        }
        local_data = pd.DataFrame(data)
        # 输入参数
        code = input("请输入期货标的（期货活跃连续合约），格式为【期货代码】+【交易所代码】，比如螺纹钢：RB.SHF ")
        code = code.upper()
        code = code.replace('.', 'ZL.')
        self.code = code
        account = input("请输入起始资金，默认值为一百万")
        if account == "":
            account = 1000000
        self.account = float(account)
        if self.code == "AGZL.SHF" or self.code == "ALZL.SHF" or self.code == "ZNZL.SHF":
            self.key_months = "01,02,03,04,05,06,07,08,09,10,11,12"
        elif self.code == "RBZL.SHF":
            self.key_months = "01,05,10"
        else:
            self.key_months = "01,05,09"
        start = local_data[local_data['Thscode'] == self.code]['Option start date'].values[0]
        end = local_data[local_data['Thscode'] == self.code]['Option end date'].values[0]
        start_date = input("请输入起始时间，（格式YYYY-MM-DD hh:mm:ss）（存在默认值，可以不输入） ")
        # 设置期权期限的判断
        start_date = Date_determination(start_date, int(start), self.key_months, 0)
        self.start_date = start_date
        end_date = input("请输入截至时间，（格式YYYY-MM-DD hh:mm:ss）（存在默认值，可以不输入） ")
        end_date = Date_determination(end_date, int(end), self.key_months, 1)
        self.end_date = end_date
        frequency = input("请输入行情的频率，60分钟则输入60，默认为30 ")
        if frequency == "":
            frequency = 30
        self.frequency = int(frequency)
        risk = input("请输入风险度（0 - 1之间的小数），默认值为0.5 ")
        if risk == "":
            risk = 0.5
        self.risk = float(risk)
        profit_stop = input("请输入止盈线（0 - 1之间的正小数），默认值为0.5 ")
        if profit_stop == "":
            profit_stop = 0.5
        self.profit_stop = float(profit_stop)
        loss_stop = input("请输入止损线（-1 - 0之间的负小数），默认值为-0.5 ")
        if loss_stop == "":
            loss_stop = -0.5
        self.loss_stop = float(loss_stop)
        quant = input("请输入分批处理的批数（正整数），只做一批：1，做多批：>1，默认值为1 ")
        if quant == "":
            quant = 1
        self.quant = int(quant)
        range_window = input("请输入滑动窗口的大小（大于0的正整数），默认值为300 ")
        if range_window == "":
            range_window = 300
        self.range_window = int(range_window)
        save_path = input("请输入保存结果文件的地址，如果没有则默认保存在当前目录下 ")
        if save_path == "":
            save_path = None
        self.save_path = save_path
        self.income = 0
        self.date_frame = None
        self.number_of_stop_losses = 0
        self.number_of_profit_exits = 0
        self.price_change_combination = local_data[local_data['Thscode'] == self.code]['Connection'].values[0]
        self.contract_multiplier = 0
        if self.contract_multiplier == 0:
            thsUrl = 'https://quantapi.51ifind.com/api/v1/date_sequence'
            flag = 0
            while True:
                date_str = self.end_date[:10]
                date1 = date.fromisoformat(date_str)
                date_final = date1 - timedelta(days=flag)
                date_str = date_final.strftime("%Y-%m-%d")
                thsPara = {"codes": self.code,
                           "startdate": date_str,
                           "enddate": date_str,
                           "functionpara":
                               {"Fill": "Blank"},
                           "indipara":
                               [
                                   {
                                       "indicator":
                                           "ths_contract_multiplier"
                                   }
                               ]
                           }
                thsResponse = requests.post(url=thsUrl, json=thsPara, headers=thsHeaders)
                try:
                    data1 = json.loads(thsResponse.content)
                    data2 = pd.json_normalize(data1['tables'])
                    data3 = data2.reset_index(drop=True)
                    data4 = data3.iloc[0, 2]
                    contract_multiplier = data4[0]
                except IndexError as e:
                    flag += 1
                    continue
                self.contract_multiplier = contract_multiplier
                break
        self.problematic_options = pd.DataFrame(columns=['code', 'question'])
        self.result = None
        self.final_frame = pd.DataFrame(columns=['range_window', 'loss_stop', 'profit_stop', 'income'])

    # 导入期货数据，并依据日期更新期货数据，实现大循环
    def Identifying_Options(self):
        # 导入时间轴
        thsUrl = 'https://quantapi.51ifind.com/api/v1/high_frequency'
        thsPara = {"codes": self.code,
                   "indicators":
                       "close,open",
                   "starttime": self.start_date,
                   "endtime": self.end_date,
                   "functionpara": {
                       "Interval": str(self.frequency),
                       "Fill": "Original"
                   }
                   }
        thsResponse = requests.post(url=thsUrl, json=thsPara, headers=thsHeaders)
        data = json.loads(thsResponse.content)
        df = pd.json_normalize(data['tables'])
        try:
            df2 = df.set_index(['thscode'])
        except Exception as e:
            print(data['errmsg'])
            print(thsPara)
            sys.exit()
        un_nested_list = []
        for col in df2.columns:
            un_nested_list.append(df2[col].apply(pd.Series).stack())
        data = pd.concat(un_nested_list, axis=1, keys=df2.columns)
        # 设置二级索引
        data = data.reset_index()
        data = data.set_index(['thscode', 'time'])
        # 格式化,行转列
        data = data.drop(columns=['level_1'])
        data = data.reset_index()
        # 设置列索引名称
        data.columns = ['thscode', 'time', 'close', 'open']
        # 去除NaN值
        data.dropna(inplace=True)
        data = data.reset_index()
        data = data.iloc[:, 1:]
        # 初步筛选阶段
        df = pd.DataFrame(columns=['C_code', 'P_code', 'time', 'close', 'upper', 'lower', 'k_call', 'k_put'])
        # 针对不同的主力月份的分类，进行区分
        list_key_months = self.key_months.split(',')
        length_key_months = len(list_key_months)
        if length_key_months == 3:
            key_month = list_key_months[2]
        for i in range(0, len(data) - self.range_window):
            # 日期的提取与转换
            timestamp_open = data.iloc[i + self.range_window, 1]
            new_row = {'time': timestamp_open}
            df.loc[i] = new_row
            index1 = timestamp_open.find('-')
            year = int(timestamp_open[:index1]) - 2000
            index2 = timestamp_open.find('-', index1 + 1)
            month = int(timestamp_open[index1 + 1:index2])
            if length_key_months == 3:
                if month < 5:
                    month = '05'
                elif month < int(key_month):
                    month = key_month
                else:
                    month = '01'
                    year = year + 1
            else:
                if month == 12:
                    year = year + 1
                    month = list_key_months[0]
                else:
                    month = list_key_months[month]
            # 依据日期来定位期货
            new_string = self.code.replace("ZL", "")
            str_list = list(new_string)
            if self.code[-3:] == "CZC":
                year = year % 10
            str_list.insert(-4, str(year) + month)
            Futures_code = ''.join(str_list)
            if i == 0:
                temp_Futures_code = Futures_code
                start_date = data.iloc[i, 1]
                count = 1
            else:
                if temp_Futures_code != Futures_code:
                    df = Import_Update_data(df, temp_Futures_code, start_date, df.iloc[i - 1, 2], count,
                                            self.price_change_combination, self.frequency, self.range_window, i - 1)
                    start_date = data.iloc[i - 1, 1]
                    count = 1
                    temp_Futures_code = Futures_code
                    # 最后一个要特殊处理
                    if i == len(data) - self.range_window - 1:
                        df = Import_Update_data(df, Futures_code, start_date, df.iloc[i, 2], count,
                                                self.price_change_combination, self.frequency, self.range_window, i)
                else:
                    count += 1
                    # 最后一个要特殊处理
                    if i == len(data) - self.range_window - 1:
                        df = Import_Update_data(df, Futures_code, start_date, df.iloc[i, 2], count,
                                                self.price_change_combination, self.frequency, self.range_window, i)
        # 按照时间筛选阶段
        date_frame = pd.DataFrame(columns=['fore_code', 'over_date', 'due_date'])  # 记录到期日的表
        if self.code[-3:] != "CZC":
            fore_code_length = 5
        else:
            fore_code_length = 4
        index1 = self.code.find('.')
        if index1 == 4:
            fore_code_length += 1
        date_flag = 0
        day_limit = 5  # 到期日前五天不做
        count = 0
        temp_t_Futures_code = None
        flag = 0
        over_flag = 0
        i = 0
        while i < len(df):
            date_str1 = df.iloc[i, 2][:10]
            date1 = date.fromisoformat(date_str1)
            if flag != 0:
                date_final = date1 - timedelta(days=flag)
                date_str1 = date_final.strftime("%Y-%m-%d")
            # 查询期权的到期时间
            if i == 0 or df.iloc[i, 0][:fore_code_length] != date_frame.iloc[date_flag - 1, 0]:
                time.sleep(0.001)
                thsUrl = 'https://quantapi.51ifind.com/api/v1/date_sequence'
                thsPara = {"codes": df.iloc[i, 0],
                           "startdate": date_str1,
                           "enddate": date_str1,
                           "functionpara":
                               {"Fill": "Blank"},
                           "indipara":
                               [
                                   {
                                       "indicator":
                                           "ths_last_strike_date_option"
                                   }
                               ]
                           }
                thsResponse = requests.post(url=thsUrl, json=thsPara, headers=thsHeaders)
                try:
                    str_date1 = json.loads(thsResponse.content)
                    str_date2 = pd.json_normalize(str_date1['tables'])
                    str_date = str_date2.reset_index(drop=True)
                    date_str = str_date.iloc[0, 2]
                    date_str2 = date_str[0]
                except IndexError as e:
                    # 要更新date_frame时出现调取那天是法定节假日，往前找
                    if flag == 0:
                        flag = 1
                    else:
                        flag += 1
                    continue
                except Exception as e:
                    print(e, thsPara, str_date1)
                    sys.exit()
                flag = 0
                date2 = date.fromisoformat(date_str2)
                date3 = date2 - timedelta(days=day_limit)
                over_date = date3.strftime("%Y-%m-%d")
                due_date = date2.strftime("%Y-%m-%d")
                time_row = {'fore_code': df.iloc[i, 0][:fore_code_length], 'over_date': over_date, 'due_date': due_date}
                date_frame.loc[date_flag] = time_row
                date_flag = date_flag + 1
                # 最后一个时间的期货到期时间要记录
                if over_flag == 1:
                    break
                if count != 0 and date1 >= date3:  # 换了之后还是过期的处理
                    temp_t_Futures_code = None
                    # 执行更换函数
                    df = Import_Update_data(df, t_Futures_code, start_date, df.iloc[i - 1, 2], count,
                                            self.price_change_combination,
                                            self.frequency, self.range_window, i - 1)
                    count = 0
            if date1 < date3:
                if count != 0:
                    temp_t_Futures_code = None
                    # 执行更换函数
                    df = Import_Update_data(df, t_Futures_code, start_date, df.iloc[i - 1, 2], count,
                                            self.price_change_combination,
                                            self.frequency, self.range_window, i - 1)
                    count = 0
            else:
                index1 = df.iloc[i, 2].find('-')
                year = int(df.iloc[i, 2][:index1]) - 2000
                index2 = df.iloc[i, 2].find('-', index1 + 1)
                month = int(df.iloc[i, 2][index1 + 1:index2])
                if length_key_months == 3:
                    if month < 5:
                        month = '05'
                    elif month < int(key_month):
                        month = key_month
                    else:
                        month = '01'
                        year = year + 1
                else:
                    if month == 12:
                        year = year + 1
                        month = list_key_months[0]
                    else:
                        month = list_key_months[month]
                # 对于到期前五天的情况要做特殊处理
                if length_key_months == 3:
                    t_year = year
                    if month == '05':
                        t_month = key_month
                    elif month == key_month:
                        t_month = '01'
                        t_year = t_year + 1
                    else:
                        t_month = '05'
                else:
                    t_year = year
                    if month == "12":
                        t_year = year + 1
                        t_month = list_key_months[0]
                    else:
                        t_month = list_key_months[int(month)]
                new_string = self.code.replace("ZL", "")
                str_list = list(new_string)
                if self.code[-3:] == "CZC":
                    t_year = t_year % 10
                str_list.insert(-4, str(t_year) + t_month)
                t_Futures_code = ''.join(str_list)
                if temp_t_Futures_code is None:
                    temp_t_Futures_code = t_Futures_code
                    start_date = data.iloc[i, 1]
                if t_Futures_code == temp_t_Futures_code:
                    count += 1
                if i == len(df) - 1:
                    df = Import_Update_data(df, t_Futures_code, start_date, df.iloc[i, 2], count,
                                            self.price_change_combination,
                                            self.frequency, self.range_window, i)
                    over_flag = 1
                    continue
            i = i + 1
        self.result = df
        self.date_frame = date_frame

    # 计算各种参数，实现小循环
    def Data_Calculate(self):
        df = self.result
        df[['action', 'call_sell_price', 'call_market_price', 'average_call_change', 'put_sell_price',
            'put_market_price',
            'average_put_change', 'sum_change', 'account', 'average_lots']] = np.zeros((len(df), 10))
        df = df.sort_values(by='time', ascending=True)
        df = df.reset_index()
        df = df.iloc[:, 1:]
        # action在最后的表中是列8，action表示的是每一次操作
        action = 1
        length = len(df)
        number_of_profit_exits = 0
        number_of_stop_losses = 0
        problematic_options_flag = 0
        i = 0
        while i < length:
            df.iloc[i, 8] = action
            # 根据action的值来做出相应的判断，第一次进来特殊判断
            if i == 0 or df.iloc[i, 8] != df.iloc[i - 1, 8]:
                # 判断是不是期权存在问题，包括数据的读取问题，假定所有的问题都能通过更换期权合约来解决
                try:
                    index1 = str(df.iloc[i, 2]).find(' ')
                    str_date = str(df.iloc[i, 2])[:index1]
                    # 看涨保证金的价格的获取
                    deposit1 = get_the_margin_price(str_date, df.iloc[i, 0])
                    # 看跌保证金的价格的获取
                    deposit2 = get_the_margin_price(str_date, df.iloc[i, 1])
                    # 形成临时价格
                    if i == 0:
                        temp_price = self.account
                        df.iloc[0, 16] = self.account
                    else:
                        temp_price = df.iloc[i - 1, 16]
                    # 计算最多可以卖多少对，对于不同的次来进行不同的手数规划（没有实现）
                    amount_lots = int(temp_price * self.risk / (deposit1 + deposit2))
                    # 获取看涨数据
                    df, data_flag = get_the_option_data(df.iloc[i, 0], df.iloc[i, 2], df.iloc[-1, 2], self.frequency, i,
                                                        df, self.date_frame, 0)
                    if data_flag == 1:
                        df.iloc[i, 16] = df.iloc[i - 1, 16]
                        df.iloc[i, 10] = 0
                        df.iloc[i, 13] = 0
                        df.iloc[i, 8] = df.iloc[i, 8] - 1
                        self.problematic_options.loc[problematic_options_flag] = {'code': df.iloc[i, 0], 'question': 2}
                        problematic_options_flag += 1
                        if i == length - 1:
                            self.income = df.iloc[i, 16] - df.iloc[0, 16]
                        i = i + 1
                        continue
                    # 获取看跌数据
                    df, data_flag = get_the_option_data(df.iloc[i, 1], df.iloc[i, 2], df.iloc[-1, 2], self.frequency, i,
                                                        df, self.date_frame, 1)
                    if data_flag == 1:
                        df.iloc[i, 16] = df.iloc[i - 1, 16]
                        df.iloc[i, 10] = 0
                        df.iloc[i, 13] = 0
                        df.iloc[i, 8] = df.iloc[i, 8] - 1
                        self.problematic_options.loc[problematic_options_flag] = {'code': df.iloc[i, 1], 'question': 2}
                        problematic_options_flag += 1
                        if i == length - 1:
                            self.income = df.iloc[i, 16] - df.iloc[0, 16]
                        i = i + 1
                        continue
                except ValueError as e:
                    df.iloc[i, 16] = df.iloc[i - 1, 16]
                    df.iloc[i, 10] = 0
                    df.iloc[i, 13] = 0
                    df.iloc[i, 8] = df.iloc[i, 8] - 1
                    self.problematic_options.loc[problematic_options_flag] = {
                        'code': df.iloc[i, 0] + "," + df.iloc[i, 1],
                        'question': 1}
                    problematic_options_flag += 1
                    if i == length - 1:
                        self.income = df.iloc[i, 16] - df.iloc[0, 16]
                    i = i + 1
                    continue
                except Exception as e:
                    print(e, i)
                    sys.exit()
                if amount_lots == 0:
                    print("现在的账户总额不足以卖出一对期权合约")
                    df.iloc[i, 16] = df.iloc[i - 1, 16]
                    df.iloc[i, 10] = 0
                    df.iloc[i, 13] = 0
                    df.iloc[i, 8] = df.iloc[i, 8] - 1
                    self.result = df
                    return
                average_lots = int(amount_lots / self.quant)
                if average_lots == 0:
                    print("现在每次的交易平均不到一手")
                    df.iloc[i, 16] = df.iloc[i - 1, 16]
                    df.iloc[i, 10] = 0
                    df.iloc[i, 13] = 0
                    df.iloc[i, 8] = df.iloc[i, 8] - 1
                    self.result = df
                    return
                df.iloc[i, 17] = average_lots
                # 建立临时list
                arr1 = np.zeros(self.quant, dtype=float)
                arr2 = np.zeros(self.quant, dtype=float)
                temp = self.quant
                df.iloc[i, 9] = df.iloc[i, 10]
                df.iloc[i, 12] = df.iloc[i, 13]
                # 对临时list进行填充
                arr1[0] = df.iloc[i, 10]
                arr2[0] = df.iloc[i, 13]
                temp = temp - 1
            elif df.iloc[i, 8] == df.iloc[i - 1, 8]:
                # 判断当前的日期在当前的期权是不是过期的或者是没有值的
                if df.iloc[i, 10] == 0 or df.iloc[i, 13] == 0:
                    df.iloc[i, 10] == 0
                    df.iloc[i, 13] == 0
                    action = action + 1
                    continue
                # 清空表
                df.iloc[i, 0] = np.nan
                df.iloc[i, 1] = np.nan
                df.iloc[i, 4] = np.nan
                df.iloc[i, 5] = np.nan
                df.iloc[i, 6] = np.nan
                df.iloc[i, 7] = np.nan
                df.iloc[i, 17] = np.nan
                # 实现多批的功能
                if temp == 0:
                    df.iloc[i, 9] = np.nan
                    df.iloc[i, 12] = np.nan
                else:
                    df.iloc[i, 9] = df.iloc[i, 10]
                    df.iloc[i, 12] = df.iloc[i, 13]
                    arr1[self.quant - temp] = df.iloc[i, 10]
                    arr2[self.quant - temp] = df.iloc[i, 13]
                    temp = temp - 1
            # 计算参数，填表
            sum1 = 0
            sum2 = 0
            number = 0
            for j in range(self.quant):
                if arr1[j] != 0:
                    sum1 = sum1 + arr1[j]
                    sum2 = sum2 + arr2[j]
                    number = number + 1
            average1 = float(sum1) / number
            average2 = float(sum2) / number
            df.iloc[i, 11] = average1 - df.iloc[i, 10]
            df.iloc[i, 14] = average2 - df.iloc[i, 13]
            # 当天一手每吨的收入
            average_now_day_income = (df.iloc[i, 11] + df.iloc[i, 14]) * number
            # 最终的每次收益指标
            df.iloc[i, 15] = (df.iloc[i, 11] + df.iloc[i, 14]) * self.contract_multiplier * number * average_lots
            # 判断条件：指的是两个期权卖出去的价差总和除以两个期权卖价总和，如果大于profit_stop或者小于loss_stop
            if df.iloc[i, 15] >= 0:
                df.iloc[i, 16] = temp_price + df.iloc[i, 15]
                if average_now_day_income / (sum1 + sum2) >= self.profit_stop:
                    number_of_profit_exits = number_of_profit_exits + 1
                    action = action + 1
            else:
                df.iloc[i, 16] = temp_price + df.iloc[i, 15]
                if average_now_day_income / (sum1 + sum2) <= self.loss_stop:
                    number_of_stop_losses = number_of_stop_losses + 1
                    action = action + 1
            if i == length - 1:
                self.income = df.iloc[i, 16] - df.iloc[0, 16]
            i = i + 1
        cols_to_delete = ['average_call_change', 'average_put_change']
        self.result = df.drop(columns=cols_to_delete, axis=1)
        self.number_of_stop_losses = number_of_stop_losses
        self.number_of_profit_exits = number_of_profit_exits

    # 保存与输出最终的文件
    def OutPut_SaveFile(self):
        annualized_yield = self.income / self.account
        percent_string = "{:.2f}%".format(annualized_yield * 100)
        win = self.number_of_profit_exits / (self.number_of_profit_exits + self.number_of_stop_losses)
        strings = ["选择的期货为" + self.code,
                   "开始时间为" + self.result.iloc[0, 2] + "  结束时间为" + self.result.iloc[-1, 2],
                   "时间的频率为" + str(self.frequency) + "分钟",
                   "止盈的比例为" + str(self.profit_stop) + "  止损的比例为" + str(self.loss_stop),
                   "止盈的次数为" + str(self.number_of_profit_exits) + "次  止损的比例为" + str(
                       self.number_of_stop_losses) + "次",
                   "起始资金为" + str(self.account) + "元", "风险度为" + str(self.risk),
                   "总收入为" + str(self.income) + "元", "胜率为" + str(win), "年化收益率为" + percent_string]
        df = pd.DataFrame(strings, columns=['Parameter'])
        new_string = self.code.replace("ZL", "")
        if self.save_path is None:
            self.save_path = "./期货" + new_string + "回测.xlsx"
        with pd.ExcelWriter(self.save_path, engine='openpyxl') as writer:
            self.result.to_excel(writer, sheet_name='Result', index=False)
            df.to_excel(writer, sheet_name='Summarize', index=False)
            self.problematic_options.to_excel(writer, sheet_name='Problem Options', index=False)
            self.date_frame.to_excel(writer, sheet_name='Option Maturity Date Table', index=False)
            self.final_frame.to_excel(writer, sheet_name='Final Frame', index=False)
        self.account = self.account + self.income

    # 一次回测的函数（包括保存数据）
    def backtest(self):
        self.Identifying_Options()
        self.Data_Calculate()
        self.OutPut_SaveFile()

    # 画时间和收益的关系图
    def show_up(self):
        # 确保Date列是日期类型
        self.result['time'] = pd.to_datetime(self.result['time'])
        # 绘制折线图
        plt.plot(self.result['time'], self.result['account'], 'b-')
        # 设置图表标题和坐标轴标签
        plt.title('账户总额与时间的关系', fontdict={'fontname': 'SimHei', 'fontsize': 14})
        plt.xlabel('时间', fontdict={'fontname': 'SimHei', 'fontsize': 14})
        plt.ylabel('账户总额', fontdict={'fontname': 'SimHei', 'fontsize': 14})
        # plt.switch_backend('agg')
        plt.tight_layout()
        plt.show()

    # 一次回测的函数（输出运行的时间，不保存数据）
    def Timing_Test(self):
        start_time = time.perf_counter()
        self.Identifying_Options()
        self.Data_Calculate()
        end_time = time.perf_counter()
        total_time = end_time - start_time
        print(f"程序的运行时间为: {total_time:.6f} 秒")

    # 多次回测（保存数据，并且保留最好（收益最高）的那一次回测的数据）
    def Variety_testing(self):
        global window1
        global window2
        global window_change
        flag = 0
        best_income = 0
        window_input = input("Please enter the range and interval values for the sliding window variation(Positive "
                             "integer),in the format 100,300,10 (default values):")
        if window_input == "":
            window1 = 100
            window2 = 310
            window_change = 10
        else:
            list_window = window_input.split(',')
            window_change = int(list_window[2])
            window1 = int(list_window[0])
            window2 = int(list_window[1]) + window_change
        outer_range = int((window2 - window1) / window_change)
        loss_input = input("Please enter the range and interval for the stop-loss line (decimals between 0 "
                           "and 1), in the format 0.2,0.5,0.1 (default values):")
        if loss_input == "":
            loss1 = -0.2
            loss2 = -0.6
            loss_change = -0.1
        else:
            list_loss = loss_input.split(',')
            loss_change = -float(list_loss[2])
            loss1 = -float(list_loss[0])
            loss2 = -float(list_loss[1]) + loss_change
        middle_range = round((loss1 - loss2) / (-loss_change))
        profit_input = input("Please enter the range and interval for the stop-profit line (decimals between 0 "
                             "and 1), in the format 0.5,0.8,0.1 (default values):")
        if profit_input == "":
            profit1 = 0.5
            profit2 = 0.9
            profit_change = 0.1
        else:
            list_profit = profit_input.split(',')
            profit_change = float(list_profit[2])
            profit1 = float(list_profit[0])
            profit2 = float(list_profit[1]) + profit_change
        inner_range = round((profit2 - profit1) / profit_change)
        # 计算总的迭代次数
        total_iterations = outer_range * middle_range * inner_range
        # 创建一个总的进度条
        with (tqdm(total=total_iterations, desc="Total Progress") as pbar):
            for i in range(window1, window2, window_change):
                self.range_window = i
                self.Identifying_Options()
                temp_df = self.result
                # 可以设定一个y=2x的下界限限定或者不设定界限，因为实际意义
                for j in np.arange(loss1, loss2, loss_change):
                    self.loss_stop = j
                    for k in np.arange(profit1, profit2, profit_change):
                        if k < abs(2 * j):
                            pbar.update(1)
                            continue
                        self.profit_stop = k
                        # 运行且比较
                        self.Data_Calculate()
                        self.final_frame.loc[flag] = {'range_window': i, 'loss_stop': j,
                                                      'profit_stop': k, 'income': self.income}
                        flag += 1
                        # 保存最好的结果
                        if self.income >= best_income or flag == 1:
                            best_income = self.income
                            best_result = self.result
                            best_number_of_stop_losses = self.number_of_stop_losses
                            best_number_of_profit_exits = self.number_of_profit_exits
                            best_profit_stop = k
                            best_loss_stop = j
                            best_problematic_options = self.problematic_options
                            best_range_window = self.range_window
                            best_date_frame = self.date_frame
                        # 清理重新设置数据
                        self.income = 0
                        self.number_of_stop_losses = 0
                        self.number_of_profit_exits = 0
                        self.result = pd.DataFrame()
                        self.result = temp_df
                        pbar.update(1)  # 每次内层循环更新总进度条
        self.income = best_income
        self.result = best_result
        self.number_of_profit_exits = best_number_of_profit_exits
        self.number_of_stop_losses = best_number_of_stop_losses
        self.problematic_options = best_problematic_options
        self.profit_stop = best_profit_stop
        self.loss_stop = best_loss_stop
        self.range_window = best_range_window
        self.date_frame = best_date_frame
        new_string = self.code.replace("ZL", "")
        self.save_path = "./期货" + new_string + "best回测" + str(flag) + ".xlsx"
        self.OutPut_SaveFile()

    # 对多次回测的数据进行筛选，并画出局部最优的散点图
    def select_draw_scatter_plot(self):
        # 创建图形对象和3D轴
        fig = plt.figure()
        ax = fig.add_subplot(111, projection='3d')
        # 对数据进行（-1，1）的归一化
        # scaler = MinMaxScaler(feature_range=(-1, 1))
        # df['income'] = scaler.fit_transform(df[['income']])
        # 计算总体平均值
        total_mean_value = self.final_frame.iloc[:, 3].mean()
        # 计算总体方差
        population_variance_value = self.final_frame.iloc[:, 3].var(ddof=0)
        # 对各个曲面进行均值和方差地计算，然后进行比较，筛选出
        temp_cv = 0
        for i in range(window1, window2, window_change):
            result = self.final_frame[self.final_frame['range_window'] == i]
            X, Y, Z = result.iloc[:, 1], result.iloc[:, 2], result.iloc[:, 3]
            # 计算平均值
            mean_value = Z.mean()
            if mean_value <= total_mean_value:
                continue
            # 计算方差（样本方差）
            variance_value = Z.var()
            if variance_value >= population_variance_value:
                continue
            cv = math.sqrt(variance_value) / mean_value
            if temp_cv == 0 or cv > temp_cv:
                temp_cv = cv
                temp_X = X
                temp_Y = Y
                temp_Z = Z
                range_window = i
        ax.scatter(temp_X, temp_Y, temp_Z, c='blue', marker='o')
        print("local_max_range_window=" + str(range_window))
        print("local_max_loss_stop=" + str(temp_X.loc[temp_Z.idxmax()]))
        print("local_max_profit_stop=" + str(temp_Y.loc[temp_Z.idxmax()]))
        print("local_max_income=" + str(temp_Z.max()))
        ax.set_title('Final Result')
        ax.set_xlabel('Loss Stop')
        ax.set_ylabel('Profit Stop')
        ax.set_zlabel('Income')
        plt.show()
